package com.guragura.game.controller;

import java.util.ArrayList;
import java.util.Random;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Rectangle;
import com.guragura.game.model.Coin;
import com.guragura.game.model.MovingObstacle;
import com.guragura.game.model.Obstacle;
import com.guragura.game.model.Player;
import com.guragura.game.model.Special;
import com.guragura.screens.GameScreen;
import com.guragura.util.Constants;
import com.guragura.util.GuraGameData;

//
//
//  Generated by StarUML(tm) Java Add-In
//
//  @ Project : GeuraGeura
//  @ File Name : Level.java
//  @ Date : 1/1/2012
//  @ Author : Rendy Bambang Junior
//
//

/**
 * Level class is the main game controller. It is constructed by its level
 * number and player. Game update runs here: generate-draw-check
 * collision-listen-move. Its generate new obstacles and specials for one screen
 * in front. Generate method called when one screen passed by running player.
 **/
public class Level {

	GameScreen screen;

	/**
	 * Constants
	 **/
	public static final int NORMAL_SPEED = 250;
	public static final int SLOW_SPEED = 150;
	public static final int MAX_LEVEL = 10;
	public static final int GAMEBG_OFFSET = 60;
	public static final int SIDE_LIMIT = 40;

	private float curAccelX = 0;
	private float curAccelZ = 0;
	private float epsilon = 1;

	private TextureRegion bgReg;
	private int bgPos1, bgPos2;
	private int leftlimit;
	private int rightlimit;
	private TextureRegion[] lifeReg;
	private TextureRegion[] levelReg;
	private TextureRegion[] numReg;
	private String scoreString;
	private int scorePos;

	Random rand;

	/**
	 * Passed player from GameApp
	 **/
	private Player player;

	/**
	 * Obstacles generated every one screen.
	 **/
	public static ArrayList<Obstacle> listOfObstacles;

	/**
	 * Coins generated every one screen.
	 **/
	public static ArrayList<Coin> listOfCoins;

	/**
	 * Special generated every one screen.
	 **/
	public static ArrayList<Special> listOfSpecials;

	/**
	 * It represents game level. Assigned when level constructed
	 **/
	private int levelNum;

	/**
	 * Last score obtained
	 */
	public int lastScore;

	/**
	 * List of time when the effects gone. This list's size will same as number
	 * of effect types. [0] -> reversal [1] -> invisible [2] -> timeup
	 **/
	private long timeup[];

	/**
	 * Maximum number of obstacle in one single screen assigned for this level.
	 **/
	private int maxNumOfObstacle;

	/**
	 * Minimum number of obstacle in one single screen assigned for this level.
	 **/
	private int minNumOfObstacle;

	/**
	 * Number of screen until player finishes the level. Set when level
	 * constructed.
	 **/
	private int distance;

	/**
	 * Moving speed. It could be affected by special effects.
	 **/
	private int speed;

	/**
	 * Constructor. - assign levelNum - assign player - assign backgroundImage -
	 * assign speed - call initLevel -> assign min-max obstacle -> assign
	 * distance
	 * 
	 * @param levelNum
	 * @param player
	 **/
	public Level(GameScreen screen) {
		levelNum = GuraGameData.lastlevel;
		listOfCoins = new ArrayList<Coin>();
		listOfObstacles = new ArrayList<Obstacle>();
		listOfSpecials = new ArrayList<Special>();
		rand = new Random();
		timeup = new long[3];

		lifeReg = new TextureRegion[Player.MAX_LIFE + 1];
		for (int i = 0; i < Player.MAX_LIFE + 1; i++) {
			lifeReg[i] = screen.guragura.atlasInterface.findRegion(
					Constants.LIFE_TEXTURE, i + 1);
		}
		levelReg = new TextureRegion[MAX_LEVEL];
		for (int i = 0; i < MAX_LEVEL; i++) {
			levelReg[i] = screen.guragura.atlasInterface.findRegion(
					Constants.LEVEL_TEXT, i + 1);
		}
		numReg = new TextureRegion[10];
		for (int i = 0; i < 10; i++) {
			numReg[i] = screen.guragura.atlasInterface.findRegion(
					Constants.SCORE_TEXT, i);
		}

		// custom values
		rightlimit = 320 - SIDE_LIMIT;
		leftlimit = SIDE_LIMIT;

		this.screen = screen;
		this.player = screen.getPlayer();
		initLevel(levelNum);
	}

	/**
	 * This method initiate level based on its number. initLevel() will
	 * generate: - maxNumOfObstacle - minNumOfObstacle - distance All number
	 * above will increase as the number level increases.
	 **/
	public void initLevel(int levelNum) {
		player.init();
		timeup[Special.THIEF] = 0;
		timeup[Special.MUSCULARMAN] = 0;
		timeup[Special.HAJJ] = 0;
		screen.camera.position.set(320 / 2, 480 / 2, 0);
		speed = NORMAL_SPEED;
		// System.out.println("level "+levelNum);
		bgReg = screen.guragura.atlasInterface.findRegion(Constants.GAME_BG,
				levelNum);

		bgPos1 = GAMEBG_OFFSET - bgReg.getRegionHeight();
		bgPos2 = 0;

		listOfCoins.clear();
		listOfObstacles.clear();
		listOfSpecials.clear();

		switch (levelNum) {
		case 1:
			minNumOfObstacle = 4;
			maxNumOfObstacle = 6;
			distance = 4;
			break;
		case 2:
			minNumOfObstacle = 4;
			maxNumOfObstacle = 6;
			distance = 4;
			break;
		case 3:
			minNumOfObstacle = 5;
			maxNumOfObstacle = 7;
			distance = 5;
			break;
		case 4:
			minNumOfObstacle = 5;
			maxNumOfObstacle = 7;
			distance = 5;
			break;
		case 5:
			minNumOfObstacle = 6;
			maxNumOfObstacle = 8;
			distance = 6;
			break;
		case 6:
			minNumOfObstacle = 6;
			maxNumOfObstacle = 8;
			distance = 6;
			break;
		case 7:
			minNumOfObstacle = 7;
			maxNumOfObstacle = 9;
			distance = 7;
			break;
		case 8:
			minNumOfObstacle = 7;
			maxNumOfObstacle = 9;
			distance = 7;
			break;
		case 9:
			minNumOfObstacle = 8;
			maxNumOfObstacle = 10;
			distance = 8;
			break;
		case 10:
			minNumOfObstacle = 8;
			maxNumOfObstacle = 10;
			distance = 8;
			break;
		}
	}

	/**
	 * Update will calls - generate - draw - check - listen - move - clean and
	 * it will check whether player win or lose
	 **/
	public void update(SpriteBatch batch, Camera camera) {
		player.run(speed);
		// System.out.println("camera "+(camera.position.y-480/2));
		// System.out.println("bgPos1 = "+bgPos1);
		// System.out.println("bgPos2 = "+bgPos2);
		// System.out.println("camera = "+(screen.camera.position.y+480/2));
		if ((camera.position.y + 480 / 2) > (bgPos2 + bgReg.getRegionHeight() - GAMEBG_OFFSET)) {
			bgPos1 = bgPos2;
			bgPos2 = bgPos2 + bgReg.getRegionHeight() - GAMEBG_OFFSET;
			generateObstacles(bgPos2);
			generateSpecials(bgPos2);
			generateCoins(bgPos2);

		}
		camera.position.add(0, speed * Gdx.graphics.getDeltaTime(), 0);

		listen();
		checkCollision();

		draw(batch);

		// check win condition
		if (player.getY() > distance
				* (bgReg.getRegionHeight() - GAMEBG_OFFSET)) {
			win();
		}
	}

	private void listen() {
		curAccelX = Gdx.input.getAccelerometerX();
		curAccelZ = Gdx.input.getAccelerometerZ();

		// posisi biasa
		if (curAccelZ > 0) {
			if (curAccelX > 0 + epsilon) {
				player.moveLeft(leftlimit, rightlimit);
			} else if (curAccelX < 0 - epsilon) {
				player.moveRight(leftlimit, rightlimit);
			}
		}
		// posisi tiduran
		else if (curAccelZ < 0) {
			if (curAccelX < 0 - epsilon) {
				player.moveLeft(leftlimit, rightlimit);
			} else if (curAccelX > 0 + epsilon) {
				player.moveRight(leftlimit, rightlimit);
			}
		}
		// posisi headstand
		else {
			// not yet implemented
		}
	}

	public void win() {
		System.out.println("WIN " + levelNum);
		lastScore = player.getScore();
		if (levelNum == MAX_LEVEL) {
			System.out.println("ABSOLUTE WIN");
			screen.exitScreen();
			levelNum = 1;
		} else {
			initLevel(++levelNum);
			screen.setToLoading();

		}
	}

	/**
	 * It generates specials for one screen in front. Randomize the number and
	 * position. Add it to listOfSpecials.
	 **/
	private void generateSpecials(int bottom) {
		// clean passed specials
		int n = 0;
		// System.out.println("camera "+(screen.camera.position.y-480/2));
		while (n < listOfSpecials.size()
				&& (listOfSpecials.get(n).getY() + listOfSpecials.get(n)
						.getRect().height) < (screen.camera.position.y - 480 / 2)) {
			// System.out.println(listOfSpecials.get(i).getX()+" "+listOfSpecials.get(i).getY());
			n++;
		}
		for (int i = 0; i < n; i++) {
			listOfSpecials.remove(0);
		}

		// randomize number of specials between 0-5
		int r = rand.nextInt(16);
		n = 0;
		// kemungkinan 0,1,2 special = 12:3:1
		if (r < 4) {
			if (r < 3) {
				n = 1;
			} else {
				n = 2;
			}
		}
		if (n > 0) {
			int d = (bgReg.getRegionHeight() - GAMEBG_OFFSET - 60) / n; // nanti
																		// 60
																		// ganti
																		// sama
																		// ukuran
																		// standar
																		// satu
																		// orang
																		// spesial
			for (int i = 0; i < n; i++) {
				listOfSpecials.add(new Special(new Rectangle(SIDE_LIMIT, bottom
						+ i * d, 320 - 2 * SIDE_LIMIT, d),
						screen.guragura.atlasInterface));
			}
		}
	}

	/**
	 * It generate obstacles for one screen in front in range of min and max
	 * number. Randomize the number and position. Add it to listOfObstacles.
	 **/
	private void generateObstacles(int bottom) {
		// clean passed obstacles
		int n = 0;
		// System.out.println("camera "+(screen.camera.position.y-480/2));
		while (n < listOfObstacles.size()
				&& (listOfObstacles.get(n).getY() + listOfObstacles.get(n)
						.getRect().height) < (screen.camera.position.y - 480 / 2)) {
			// System.out.println(listOfSpecials.get(i).getX()+" "+listOfSpecials.get(i).getY());
			n++;
		}
		for (int i = 0; i < n; i++) {
			listOfObstacles.remove(0);
		}

		// randomize number between min max
		Random rand = new Random();
		n = minNumOfObstacle
				+ rand.nextInt(maxNumOfObstacle - minNumOfObstacle);
		int d = (bgReg.getRegionHeight() - GAMEBG_OFFSET - 60) / n; // nanti 60
																	// ganti
																	// sama
																	// ukuran
																	// standar
																	// satu
																	// orang
																	// spesial
		for (int i = 0; i < n; i++) {
			int varian = rand.nextInt(5);
			switch (varian) {
			case 0:
				listOfObstacles.add(new MovingObstacle(new Rectangle(
						SIDE_LIMIT, bottom + i * d, 320 - 2 * SIDE_LIMIT, d),
						screen));
				break;
			default:
				listOfObstacles
						.add(new Obstacle(new Rectangle(SIDE_LIMIT, bottom + i
								* d, 320 - 2 * SIDE_LIMIT, d), screen, false));
				break;
			}
		}
	}

	/**
	 * It generates coins for one screen in front. Randomize the number, amount
	 * of score, and position. Add it to listOfCoins.
	 **/
	private void generateCoins(int bottom) {
		// clean passed coin
		int n = 0;
		// System.out.println("camera "+(screen.camera.position.y-480/2));
		while (n < listOfCoins.size()
				&& (listOfCoins.get(n).getY() + listOfCoins.get(n).getRect().height) < (screen.camera.position.y - 480 / 2)) {
			// System.out.println(listOfSpecials.get(i).getX()+" "+listOfSpecials.get(i).getY());
			n++;
		}
		for (int i = 0; i < n; i++) {
			listOfCoins.remove(0);
		}

		// randomize number of specials between 0-5
		n = 1 + rand.nextInt(5);
		int d = (bgReg.getRegionHeight() - GAMEBG_OFFSET - 60) / n; // nanti 60
																	// ganti
																	// sama
																	// ukuran
																	// standar
																	// satu
																	// orang
																	// spesial
		for (int i = 0; i < n; i++) {
			listOfCoins.add(new Coin(new Rectangle(SIDE_LIMIT, bottom + i * d,
					320 - 2 * SIDE_LIMIT, d), screen.guragura.atlasInterface));
		}
	}

	/**
	 * It draws: - player - obstacles - specials - current level - current score
	 * - current life - current distance bar
	 **/
	private void draw(SpriteBatch batch) {
		batch.begin();
		// draw bg
		// System.out.println("drawbgPos1 = "+bgPos1);
		// System.out.println("drawbgPos2 = "+bgPos2);
		batch.draw(bgReg, 0, bgPos1);
		batch.draw(bgReg, 0, bgPos2);

		// draw obstacles
		for (Obstacle obstacle : listOfObstacles) {
			obstacle.draw(batch);
		}

		// draw coins
		for (Coin coin : listOfCoins) {
			coin.draw(batch);
		}

		// draw specials
		for (Special special : listOfSpecials) {
			special.draw(batch);
		}

		// draw players
		player.draw(batch);
		// draw info
		scoreString = String.valueOf(player.getScore());
		scorePos = 300 - scoreString.length() * numReg[0].getRegionWidth();
		for (int i = 0; i < scoreString.length(); i++) {
			batch.draw(numReg[scoreString.charAt(i) - '0'], scorePos + i
					* numReg[0].getRegionWidth(), player.getY() + 407);
		}
		// screen.font.draw(batch,
		// "score "+player.getScore(),10,200+screen.camera.position.y);
		batch.draw(levelReg[levelNum - 1],
				160 - levelReg[levelNum - 1].getRegionWidth() / 2,
				player.getY() + 400);
		if (player.getLife() >= 0) {
			batch.draw(lifeReg[player.getLife()], 10, player.getY() + 395);
			batch.draw(numReg[player.getLife()],
					20 + lifeReg[player.getLife()].getRegionWidth(),
					player.getY() + 405);
		}
		// screen.font.draw(batch,
		// "life "+player.getLife(),10,180+screen.camera.position.y);
		// screen.font.draw(batch,
		// "lv. "+levelNum,10,160+screen.camera.position.y);
		// screen.font.draw(batch,
		// "total distance "+distance*(bgReg.getRegionHeight()-GAMEBG_OFFSET),10,160+screen.camera.position.y);
		// screen.font.draw(batch,
		// "current distance "+player.getY(),10,140+screen.camera.position.y);

		/*
		 * screen.font.drawMultiLine(batch, "accel: [" +
		 * Gdx.input.getAccelerometerX() + "," + Gdx.input.getAccelerometerY() +
		 * "," + Gdx.input.getAccelerometerZ() + "]\n" + "orientation: " +
		 * Gdx.input.getNativeOrientation() + "\n" + "rotation: " +
		 * Gdx.input.getRotation() + "\n" + "wh: " +
		 * Gdx.graphics.getDesktopDisplayMode() + "\n", 0,
		 * 100+screen.camera.position.y);
		 */
		batch.end();
	}

	/**
	 * It checks collision between player and - coins - obstacles (if player is
	 * not invisible) - calls check effect set hit once it's been hit by player
	 **/
	private void checkCollision() {
		// check collision except specials
		for (int i = 0; i < listOfObstacles.size(); i++) {
			Obstacle obstacle = listOfObstacles.get(i);
			if (!obstacle.isDeathType()) {
				if (!player.isInvisible() && !obstacle.isHit()
						&& obstacle.isCollide(player)) {
					obstacle.setHit();
					screen.guragura.vibrator.vibrate();
					player.decLife();
					if (player.getLife() < 0) {
						lose();
					}
				}
			} else {
				if (obstacle.isCollide(player)) {
					screen.sound.playTereakjatoh();
					screen.guragura.vibrator.vibrate();
					lose();
					break;
				}
			}
		}
		ArrayList<Coin> toRemove = new ArrayList<Coin>();
		for (int i = 0; i < listOfCoins.size(); i++) {
			if (player.isCollide(listOfCoins.get(i))) {
				screen.sound.playCling();
				player.incScore(listOfCoins.get(i).getScore());
				System.out.println("coin +" + listOfCoins.get(i).getScore());
				toRemove.add(listOfCoins.get(i));
				screen.guragura.vibrator.vibrate();
			}
		}
		for (int i = 0; i < toRemove.size(); i++) {
			listOfCoins.remove(toRemove.get(i));
		}
		checkEffect();
	}

	public  void lose() {
		System.out.println("LOSE");		
		System.out.println("sebelum proses hs" + GuraGameData.highscore);
		player.lastHighScore = GuraGameData.highscore;
		if (GuraGameData.highscore >=0
				&& player.getScore() > GuraGameData.highscore) {		
			GuraGameData.highscore = lastScore;
		}
		GuraGameData.saveSettings();
		System.out.println("setelah proses hs" + GuraGameData.highscore);

		initLevel(1);
		screen.exitScreen();
	}

	/**
	 * Check if player hit special and decide the effect - thief -> reversal
	 * move - expectant -> life +1 - muscularman -> invisible - hajj -> slow
	 * motion -> assign it to listOfEffectTimeUp
	 * 
	 * Check listOfTimeUpEffect, in case of there's any time up effect
	 **/
	private void checkEffect() {
		// check collision with effect and decide effect
		for (Special special : listOfSpecials) {
			if (!special.isHit() && player.isCollide(special)) {
				special.setEffect(this);
				screen.sound.playHitspecial();
				switch (special.getType()) {
				case Special.THIEF:
					screen.sound.playPencurikena();
					break;
				case Special.EXPECTANT:
					screen.sound.playIbukena();
					break;
				case Special.MUSCULARMAN:
					screen.sound.playOtotkena();
					break;
				case Special.HAJJ:
					screen.sound.playAkikena();
					break;
				}
				System.out.println("seteffect " + special.getType());
				if (special.getType() < 3)
					timeup[special.getType()] = System.currentTimeMillis()
							+ special.getDuration();
				special.setHit();
				screen.guragura.vibrator.vibrate();
			}
		}
		for (int i = 0; i < 3; i++) {
			if (timeup[i] != 0 && timeup[i] < System.currentTimeMillis()) {
				Special.unsetEffect(i, this);
				timeup[i] = 0;
				System.out.println("unseteffect " + i);
			}
		}
	}

	/**
	 * Player getter
	 **/
	public Player getPlayer() {
		return player;
	}

	/**
	 * Set slow
	 **/
	public void setSpeedSlow() {
		speed = SLOW_SPEED;
	}

	/**
	 * Set normal
	 **/
	public void setSpeedNormal() {
		speed = NORMAL_SPEED;
	}

	public static int getNormalSpeed() {
		return NORMAL_SPEED;
	}

	public static int getSlowSpeed() {
		return SLOW_SPEED;
	}

	public int getLevelNum() {
		return levelNum;
	}

	public static ArrayList<Obstacle> getListOfObstacles() {
		return listOfObstacles;
	}

	public static ArrayList<Coin> getListOfCoins() {
		return listOfCoins;
	}

	public static ArrayList<Special> getListOfSpecials() {
		return listOfSpecials;
	}

	public int getDistance() {
		return distance;
	}

	public int getSpeed() {
		return speed;
	}

	public int getLeftLimit() {
		return leftlimit;
	}

	public int getRightLimit() {
		return rightlimit;
	}
}
